﻿/*
* Hyphenator.as - for Flash Player 10
* Copyright (C) 2011 David Moldenhauer (dave at multiflaviour dot com)
* Project and Source hosted on http://code.google.com/p/hyphenator-flash/
* 
* This ActionScript code is free software: you can redistribute it 
* and/or modify it under the terms of the GNU Lesser General Public 
* License (GNU LGPL) as published by the Free Software Foundation, 
* either version 3 of the License, or (at your option) any later version.
* 
* This code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
* 
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Hyphenator.as is a partial port of the javascript library 
* Hyphenator.js by Mathias Nater (mathias at mnn dot ch), which is
* also released under GNU LGPL.
*
* Hyphenator X.Y.Z - client side hyphenation for webbrowsers
* Copyright (C) 2010  Mathias Nater, Zürich (mathias at mnn dot ch)
* Project and Source hosted on http://code.google.com/p/hyphenator/
*
*  Hyphenator.js and Hyphenator.as contains code from Bram Steins hypher.js-Project:
*  https://github.com/bramstein/Hypher
*  
*  Code from this project is marked in the source and belongs 
*  to the following license:
*  
*  Copyright (c) 2011, Bram Stein
*  All rights reserved.
*  
*  Redistribution and use in source and binary forms, with or without 
*  modification, are permitted provided that the following conditions 
*  are met:
*   
*   1. Redistributions of source code must retain the above copyright
*      notice, this list of conditions and the following disclaimer. 
*   2. Redistributions in binary form must reproduce the above copyright 
*      notice, this list of conditions and the following disclaimer in the 
*      documentation and/or other materials provided with the distribution. 
*   3. The name of the author may not be used to endorse or promote products 
*      derived from this software without specific prior written permission. 
*  
*  THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED 
*  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
*  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 
*  EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
*  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
*  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
*  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 
*  OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
*  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
*  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package  {
	import fl.text.TLFTextField;
	
	import flash.text.TextField;
	import flash.utils.getTimer;
	
	public class Hyphenator {
		
		private static var instance:Hyphenator;
		
		public var cachedTextField:Array = new Array();
		public var indexTextField:int = 0;
		
		public var enableReducedPatternSet:Boolean = false;
		public var time:Number = 0;
		public var hyphen:String = String.fromCharCode(0x2D);
		public var nonBreakHyphen:String = String.fromCharCode(0x2011);
		public var softHyphen:String = String.fromCharCode(0xAD);
		public var enableCache:Boolean = true;
		public var exceptions:Object = new Object();
		private var languages:Object = new Object();
		private var min:int = 6;
		private var url:String = '(\\w*:\/\/)?((\\w*:)?(\\w*)@)?((([\\d]{1,3}\\.){3}([\\d]{1,3}))|(([\\w]*\\.)+([\\w]{2,4})))(:\\d*)?(\/[\\w#!:\\.?\\+=&%@!\\-]*)*';
		private var mail:String = '[\\w-\\.]+@[\\w\\.]+';
		private var lastCharRegExp:RegExp = /[^a-zA-Z0-9]/g;
		private var urlOrMailRE:RegExp = new RegExp('(' + url + ')|(' + mail + ')', 'i');
		private var urlhyphen:String = String.fromCharCode(0x200B);

		public static function set minWordLength(val:int):void{
			hyphenator.min = val;
		}
		public static function get minWordLength():int{
			return hyphenator.min;
		}
		
		public static function set language(obj:Object):void{
			hyphenator.languages = obj;
		}
		public static function get language():Object{
			return hyphenator.languages;
		}
		
		private static function get hyphenator():Hyphenator{
			if (instance == null) {
				instance = new Hyphenator(new SingletonEnforcer());
			}
			return instance;
		}
		
		public function Hyphenator(key:SingletonEnforcer):void {
			if(key == null){
				throw new Error("Initialization failed: the Constructor should not be called");
			}
		}
		
		public static function hyphenateTLFTextField(target:TLFTextField, lang:String):void{
			//hyphenator.time = getTimer();
			var htmlText:Vector.<Vector.<String>> = hyphenator.seperateTags((target.htmlText), lang, true);
			var targetText:String = "";
			for(var i:int = 0; i < htmlText[0].length; i++){
				if(htmlText[0][i] == ""){
					if(htmlText[1][i] != ""){targetText += htmlText[1][i];}
					continue;
				}else{
					if(htmlText[0][i] != ""){targetText += htmlText[0][i];}
					continue;
				}
			}
			//targetText += (" -> " + String(getTimer()-hyphenator.time) + " ms");
			target.htmlText = targetText;
		}
		
		public static function hyphenateTextField(target:TextField, lang:String):void{
			//hyphenator.time = getTimer();
			for(var i:int = 0; i < hyphenator.cachedTextField.length; i++){
				if((target === hyphenator.cachedTextField[i][0])&&(lang == hyphenator.cachedTextField[i][2])){
					hyphenator.updateTextField(hyphenator.cachedTextField[i][0], hyphenator.cachedTextField[i][1]);
					return;
				}
			}
			hyphenator.cachedTextField.push([target, hyphenator.seperateTags((target.htmlText), lang), lang]);
			hyphenator.updateTextField(target, hyphenator.cachedTextField[hyphenator.cachedTextField.length-1][1]);
		}
		
		private function updateTextField(target:TextField, text:Vector.<Vector>):void{
			var lastIndex:int = 0;
			var lastTextIndex:int = 0;
			var currentIndex:int = 0;
			var currentText:String = "";
			target.htmlText = "";
			for(var i:int = 0; i < text[0].length; i++){
				if(text[0][i] == ""){
					if(text[1][i] != ""){currentText += text[1][i];}
					continue;
				}
				var hyphenParts:Array = text[0][i].split(hyphen);
				for(var m:int = 0; m < hyphenParts.length; m++)
				{
					var spaceParts:Array = hyphenParts[m].split(" ");
					for(var sp:int = 0; sp < spaceParts.length; sp++){
						currentText += spaceParts[sp];
						if(currentText.length != spaceParts[sp].length){
							if(sp != spaceParts.length-1){
								target.htmlText = currentText + " ";
							}else{
								target.htmlText = currentText;
							}
							if((sp == 0)&&(0 != m)){
								if(target.getLineIndexOfChar(target.text.length-2) == target.getLineIndexOfChar(lastTextIndex-1)){
									currentText = currentText.substring(0, lastIndex) + currentText.substring(lastIndex+1);
								}else{
									if(lastCharRegExp.test(currentText.charAt(lastIndex-1))){
										currentText = currentText.substring(0, lastIndex) + currentText.substring(lastIndex+1);
									}
								}
							}
						}
						if(sp != spaceParts.length-1){
							currentText += " ";
						}
					}
					lastIndex = currentText.length;
					if(hyphenParts.length-1 != m){
						currentText += hyphen;
					}
					lastTextIndex = target.text.length;
					//target.htmlText = currentText;
				}
				if(text[1][i] != ""){currentText += text[1][i]};
				target.htmlText = currentText;
			}
			//target.appendText(" -> " + String(getTimer()-time) + " ms");
		}
		
		private function seperateTags(txt:String, lang:String, TLF:Boolean = false):Vector.<Vector.<String>>
		{
			var returnVal:Vector.<Vector.<String>> = new Vector.<Vector.<String>>();
			var text:Vector.<String> = new Vector.<String>();
			var tags:Vector.<String> = new Vector.<String>();
			var index:int = -1;
			var index2:int = -1;
			var index3:int = 0;
			var tlfExeption:String = "";
			if(TLF){
				tlfExeption = hyphenator.hyphen;
				hyphenator.hyphen = hyphenator.softHyphen;
				hyphenator.softHyphen = tlfExeption;
			}
			while((index = txt.indexOf("<", index + 1)) != -1)
			{
				index2 = txt.indexOf(">", index + 1);
				text.push(hyphenate(txt.substring(index3, index), lang));
				tags.push(txt.substring(index, index2 + 1));
				index3 = index2+1;
			}if(index3 < txt.length){
				text.push(hyphenate(txt.substring(index3), lang));
				tags.push("");
			}
			if(TLF){
				tlfExeption = hyphenator.hyphen;
				hyphenator.hyphen = hyphenator.softHyphen;
				hyphenator.softHyphen = tlfExeption;
			}
			returnVal.push(text, tags);
			return returnVal;
		}
		
		public static function hyphenate(target:String, lang:String):String{
			if(!hyphenator.languages[lang].prepared){
				hyphenator.prepareLanguagesObj(lang);
			}
			if((target is String)){
				return target.replace(hyphenator.languages[lang].genRegExp, hyphenating);
			}
			function hyphenating():String{
				for(var str:String in arguments){
					if(hyphenator.urlOrMailRE.test(arguments[str])){
						return hyphenator.hyphenateURL(arguments[str]);
					}else{
						return hyphenator.hyphenateWord(lang, arguments[str]);
					}
				}
				throw new Error('expeced a string');
				return null;
			}
			return null;
		}
		
		private function prepareLanguagesObj(lang:String):void{
			var lo:Object = languages[lang];
			var wrd:String;
			if(!lo.prepared){
				if(enableCache){
					lo.cache = new Object();
				}
				if(enableReducedPatternSet){
					lo.redPatSet = new Object();
				}
				if(lo.hasOwnProperty('exceptions')){
					addExceptions(lang, lo.exceptions);
					delete lo.exceptions;
				}
				if (exceptions.hasOwnProperty('global')) {
					if (exceptions.hasOwnProperty(lang)) {
						exceptions[lang] += ', ' + exceptions.global;
					} else {
						exceptions[lang] = exceptions.global;
					}
				}
				if (exceptions.hasOwnProperty(lang)) {
					lo.exceptions = convertExceptionsToObject(exceptions[lang]);
					delete exceptions[lang];
				} else {
					lo.exceptions = new Object();
				}
				convertPatterns(lang);
				wrd = '[\\w' + lo.specialChars + '@' + String.fromCharCode(173) + '-]{' + min + ',}';
				lo.genRegExp = new RegExp('(' + url + ')|(' + mail + ')|(' + wrd + ')', 'gi');
				lo.prepared = true;
			}
		}
		
		private function convertPatterns(lang:String):void{
			/** @license BSD licenced code
			 * The following code is based on code from hypher.js and adapted for Hyphenator.js
			 * Copyright (c) 2011, Bram Stein
			 */
			var tree:Object = {tpoints:new Array()};
			var patterns:Array;
			var pattern:String;
			var patternObject:Object = Hyphenator.language[lang].patterns;
			var c:String;
			var chars:Array;
			var points:Array;
			var t:Object;
			var p:String;
			var codePoint:int;
			for (var size:String in patternObject){
				if(patternObject.hasOwnProperty(size)){
					patterns = patternObject[size].match(new RegExp('.{1,' + (+int(size)) + '}', 'g'));
					var i:int = 0;
					while((pattern = patterns[i++])){
						chars = pattern.replace(/[\d]/g, '').split('');
						points = pattern.split(/\D/);
						t = tree;
						
						var j:int = 0;
						while((c = chars[j++])){
							codePoint = c.charCodeAt(0);
							
							if(!t[codePoint]){
								t[codePoint] = {};
							}
							t = t[codePoint];
						}
						
						t.tpoints = new Array();
						for(var k:int = 0; k < points.length; k++){
							p = points[k];
							t.tpoints.push((p == '') ? 0 : p);
						}
					}
				}
			}
			languages[lang].patterns = tree;
			languages[lang].patternsConverted = true;
			/**
			 * end of BSD licenced code from hypher.js
			 */
		}
		
		private function recreatePattern(pattern:String, nodePoints:Array):String{
			var r:Array = new Array();
			var c:Array = pattern.split('');
			for(var i:int = 0; i < nodePoints.length; i++){
				if(nodePoints[i] !== 0){
					r.push(nodePoints[i]);
				}
				if(c[i]){
					r.push(c[i]);
				}
			}
			return r.join('');
		}
		
		public static function addExceptions(lang:String, words:String):void{
			if(lang === ''){
				lang = 'global';
			}
			if(hyphenator.exceptions.hasOwnProperty(lang)){
				hyphenator.exceptions[lang] += ", " + words;
			}else{
				hyphenator.exceptions[lang] = words;
			}
		}
		
		private function convertExceptionsToObject(exc:String):Object {
			var w:Array = exc.split(', ');
			var r:Object = new Object();
			var key:String;
			for (var i:int = 0; i < w.length; i++) {
				key = w[i].replace(/-/g, '');
				if (!r.hasOwnProperty(key)) {
					r[key] = w[i];
				}
			}
			return r;
		}
		
		private function hyphenateURL(url:String):String {
			return url.replace(/([:\/\.\?#&_,;!@]+)/gi, '$&' + urlhyphen);
		}
		
		private function hyphenateWord(lang:String, word:String):String{
			var lo:Object = languages[lang];
			var parts:Array;
			var subst:String;
			var w:String;
			var characters:Array;
			var originalCharacters:Array;
			var wordLength:int;
			var j:int;
			var k:int;
			var node:Object;
			var points:Array = [];
			var characterPoints:Array = [];
			var nodePoints:Array;
			var nodePointsLength:int;
			var trie:Object;
			var result:Array = [''];
			var pattern:String;
			var i:int;
			
			if(word == ''){
				return '';
			}
			if(word.indexOf(String.fromCharCode(0xAD)) !== -1){
				return word;
			}
			if(enableCache && lo.cache.hasOwnProperty(word)){
				//the word is in the cache
				return lo.cache[word];
			}
			if(lo.exceptions.hasOwnProperty(word)){
				//the word is in the exceptions list
				return lo.exceptions[word].replace(new RegExp(/-/g), hyphen);
			}
			if(word.indexOf('-') !== -1){
				//word contains '-' -> hyphenate the parts separated with '-'
				parts = word.split('-');
				for(i = 0; i < parts.length; i++){
					parts[i] = hyphenateWord(lang, parts[i]);
				}
				return parts.join(softHyphen);
			}
			//finally the core hyphenation algorithm
			w = word = '_' + word + '_';
			if(lo.charSubstitution){
				for(subst in lo.charSubstitution){
					if(lo.charSubstitution.hasOwnProperty(subst)){
						w = w.replace(new RegExp(subst, 'g'), lo.charSubstitution[subst]);
					}
				}
			}
			if(word.indexOf("'") !== -1){
				w = w.replace("'", "’");//replace APOSTROPHE with RIGHT SINGLE QUOTATION MARK (since the latter is used in the patterns)
			}
			/** @license BSD licenced code
			 * The following code is based on code from hypher.js
			 * Copyright (c) 2011, Bram Stein
			 */
			 characters = w.toLowerCase().split('');
			 originalCharacters = word.split('');
			 wordLength = characters.length;
			 trie = lo.patterns;
			 for(i = 0; i < wordLength; i++){
				 points[i] = 0;
				 characterPoints[i] = characters[i].charCodeAt(0);
			 }
			 for(i = 0; i < wordLength; i++){
				 pattern = '';
				 node = trie;
				 for(j = i; j < wordLength; j++){
					 node = node[characterPoints[j]];
					 if(node){
						 if(enableReducedPatternSet){
						 	pattern += String.fromCharCode(characterPoints[j]);
						 }
						 nodePoints = node.tpoints;
						 if(nodePoints){
							 if(enableReducedPatternSet){
								 if(!lo.redPatSet){
									 lo.redPatSet = {};
								 }
								 lo.redPatSet[pattern] = recreatePattern(pattern, nodePoints);
							 }
							 for(k = 0; k < nodePoints.length; k++){
								 points[i+k] = Math.max(points[i+k], nodePoints[k]);
							 }
						 }
					 } else{
						 break;
					 }
				 }
			 }
			 for(i = 1; i < wordLength - 1; i++){
				 if(i > lo.leftmin && i < (wordLength - lo.rightmin) && points[i] % 2){
					 result.push(originalCharacters[i]);
				 }else {
					 result[result.length-1] += originalCharacters[i];
				 }
			 }
			 return result.join(hyphen);
			/**
			 * end of BSD licenced code from hypher.js
			 */
		}

	}

}
internal class SingletonEnforcer{}